﻿// Copyright (c) Microsoft.  All Rights Reserved.  Licensed under the Apache License, Version 2.0.  See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Test.Utilities;
using Microsoft.CodeAnalysis.CSharp.UnitTests.Emit;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Test.Utilities;
using Xunit;

namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
{
    public partial class CodeGenOptimizedNullableOperatorTests : CSharpTestBase
    {
        [Fact]
        public void TestNullableBoxingConversionsAlwaysNull()
        {
            // The native compiler does not optimize this case; Roslyn does. We know
            // that the result of boxing default(int?) to object is the same as casting
            // literal null to object, so we do not need to allocate space on the stack 
            // for the nullable int, initialize it, and then box that to a null ref.

            string[] sources = {
@"class Program
{
    static void Main()
    {
        System.Console.WriteLine((object)default(int?));
    }
}
",

@"class Program
{
    static void Main()
    {
        System.Console.WriteLine((object)(new int?()));
    }
}
",

@"class Program
{
    static void Main()
    {
        System.Console.WriteLine((object)(int?)null);
    }
}
"};

            string expectedOutput = "";
            string expectedIL = @"{
  // Code size        7 (0x7)
  .maxstack  1
  IL_0000:  ldnull
  IL_0001:  call       ""void System.Console.WriteLine(object)""
  IL_0006:  ret
}";

            foreach (string source in sources)
            {
                var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
                comp.VerifyDiagnostics();
                comp.VerifyIL("Program.Main", expectedIL);
            }
        }

        [Fact]
        public void TestNullableBoxingConversionNeverNull()
        {
            // The native compiler does not optimize this case; Roslyn does. We know
            // that the result of boxing default(int?) to object is the same as casting
            // literal null to object, so we do not need to allocate space on the stack 
            // for the nullable int, initialize it, and then box that to a null ref.

            string[] sources = {
@"class Program
{
    static void Main()
    {
        System.Console.WriteLine((object)new int?(123));
    }
}
",

@"class Program
{
    static void Main()
    {
        System.Console.WriteLine((object)(int?)123);
    }
}
"};

            string expectedOutput = "123";
            string expectedIL = @"{
  // Code size       13 (0xd)
  .maxstack  1
  IL_0000:  ldc.i4.s   123
  IL_0002:  box        ""int""
  IL_0007:  call       ""void System.Console.WriteLine(object)""
  IL_000c:  ret
}";

            foreach (string source in sources)
            {
                var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
                comp.VerifyDiagnostics();
                comp.VerifyIL("Program.Main", expectedIL);
            }
        }

        [Fact]
        public void TestNullableConversionAlwaysNull()
        {
            // A built-in nullable conversion whose argument is known to always be null
            // can simply be optimized away to be the null result.

            string source = @"
class Program
{
    static long? M()
    {
        return new int?();
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       10 (0xa)
  .maxstack  1
  .locals init (long? V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""long?""
  IL_0008:  ldloc.0
  IL_0009:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestNullableConversionNeverNull()
        {
            // A built-in nullable conversion whose argument is known to be non-null
            // can be generated by converting the value to the underlying target type,
            // and then converting that to nullable, without generating the nullable source
            // or checking to see if it has a value.

            string source = @"
class Program
{
    static long? M(int x)
    {
        return new int?(x);
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  ldarg.0
  IL_0001:  conv.i8
  IL_0002:  newobj     ""long?..ctor(long)""
  IL_0007:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestLiftedUserDefinedConversionAlwaysNull()
        {
            // A user-defined nullable conversion whose argument is known to always be null
            // can simply be optimized away to be the null result.

            string source = @"
struct S
{
    public static implicit operator S(int x) { return new S(); }
}
class Program
{
    static S? M()
    {
        return new int?();
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       10 (0xa)
  .maxstack  1
  .locals init (S? V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""S?""
  IL_0008:  ldloc.0
  IL_0009:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestLiftedUserDefinedConversionNeverNull()
        {
            // A user-defined nullable conversion whose argument is known to never be null
            // can have the nullable ctor, temporary store and value test optimized away.

            string source = @"
struct S
{
    public static implicit operator S(int x) { return new S(); }
    public static implicit operator string(S s) { return s.ToString(); }
}


class Program
{
    static S? M1(int x)
    {
        return new int?(x);
    }

    static S M2(int x)
    {
        return (S)(new int?(x));
    }

    static string M3(int x)
    {
        // The non-null conversion optimizer should chain well.  That is,
        // we first optimize (string)(S?)(new int?(x)) to (string)(new S?((S)x)), and then
        // to (string)(S)x.

        return (string)(S?)(new int?(x));
    }

    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       12 (0xc)
  .maxstack  1
  IL_0000:  ldarg.0
  IL_0001:  call       ""S S.op_Implicit(int)""
  IL_0006:  newobj     ""S?..ctor(S)""
  IL_000b:  ret
}";
            string expectedIL2 = @"{
  // Code size        7 (0x7)
  .maxstack  1
  IL_0000:  ldarg.0
  IL_0001:  call       ""S S.op_Implicit(int)""
  IL_0006:  ret
}";

            string expectedIL3 = @"{
  // Code size       12 (0xc)
  .maxstack  1
  IL_0000:  ldarg.0
  IL_0001:  call       ""S S.op_Implicit(int)""
  IL_0006:  call       ""string S.op_Implicit(S)""
  IL_000b:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
        }

        [Fact]
        public void TestNullableUnaryOpsAlwaysNull()
        {
            // A unary operator whose argument is known to always be null
            // can simply be optimized away to be the null result.

            string source = @"
class Program
{
    static int? M()
    {
        return ~(new int?());
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       10 (0xa)
  .maxstack  1
  .locals init (int? V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""int?""
  IL_0008:  ldloc.0
  IL_0009:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics(
// (6,16): warning CS0458: The result of the expression is always 'null' of type 'int?'
//         return ~(new int?());
Diagnostic(ErrorCode.WRN_AlwaysNull, "~(new int?())").WithArguments("int?"));
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestNullableUnaryOpsAlwaysNullChained()
        {
            // A unary operator whose argument is known to always be null
            // can simply be optimized away to be the null result. These
            // optimizations should "chain" naturally. Here we combine
            // a built-in conversion, two unary operations, and a boxing.
            // The net result should simply be a null reference.
            //
            // The native compiler does not handle these "chained" optimizations,
            // interestingly enough; the native compiler will optimize away only the
            // innermost one; it is then not treated as "always null" and is checked
            // for nullity unnecessarily.
            //
            // Fortunately, the warning logic does not do a deep analysis; it only
            // reports a single warning.

            string source = @"
class Program
{
    static object M()
    {
        return ~-(new short?());
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size        2 (0x2)
  .maxstack  1
  IL_0000:  ldnull
  IL_0001:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics(
    // (6,17): warning CS0458: The result of the expression is always 'null' of type 'int?'
    //         return ~-(new short?());
    Diagnostic(ErrorCode.WRN_AlwaysNull, "-(new short?())").WithArguments("int?"));
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestNullableUnaryOpsNeverNull()
        {
            // A unary operator whose argument is known to never be null
            // can be optimized to avoid the null check.

            string source = @"
class Program
{
    static int N() { return 123; }
    static int? M() 
    { 
        // This can be optimized to new int?(~N())
        return ~(new int?(N())); 
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       12 (0xc)
  .maxstack  1
  IL_0000:  call       ""int Program.N()""
  IL_0005:  not
  IL_0006:  newobj     ""int?..ctor(int)""
  IL_000b:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestNullableUnaryOpsNeverNullChained()
        {
            // A unary operator whose argument is known to never be null
            // can simply be optimized away to be the null result. These
            // optimizations should "chain" naturally. Here we combine
            // two unary operations and a boxing. As you can see, we eliminate
            // all the null checks and the "new S?" ctor.

            string source = @"
struct S
{
  public static S operator +(S s) { return s; }
  public static S operator -(S s) { return s; }
  public static S operator ~(S s) { return s; }
}

class Program
{
    static object M(S s)
    {
        return ~-(new S?(s));
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       17 (0x11)
  .maxstack  1
  IL_0000:  ldarg.0
  IL_0001:  call       ""S S.op_UnaryNegation(S)""
  IL_0006:  call       ""S S.op_OnesComplement(S)""
  IL_000b:  box        ""S""
  IL_0010:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestLiftedUnaryOpOnTopOfLifted()
        {
            // Here's an optimization that the dev10 compiler does not do. If we have a 
            // lifted unary operator "on top" of another lifted operation, then the unary
            // operation can be "distributed" to both branches of the underlying lifted operation.
            //
            // For example, suppose we have
            //
            // return -(N1() + N2());
            //
            // Where N1() and N2() return int?. The dev10 compiler does this in two steps: first it 
            // computes the int? result of the addition, and then it does a fully lifted negation:
            //
            // int? t1 = N1();
            // int? t2 = N2();
            // int? t3 = t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?();
            // return t3.HasValue ? new int?(-t3.Value)) : new int?();
            //
            // But t3 is completely unnecessary here. We could realize this as:
            //
            // return -(t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?())
            //
            // which is the same as distributing the conversion to the consequence and alternative:
            //
            // return (t1.HasValue && t2.HasValue ? - new int?(t1.Value + t2.Value) ): - ( new int?() ) )
            // 
            // and now we can optimize the consequence and alternative down to
            //
            // return (t1.HasValue && t2.HasValue ? new int?(-(t1.Value + t2.Value) ): new int?() )
            //
            // And the int? t3 disappears entirely. 
            //
            // This optimization has the nice property that it composes well with itself, as we'll see.

            string source = @"
struct S
{
  public static S operator -(S s) { return s; }
  public static S operator ~(S s) { return s; }
  public static S operator +(S s1, S s2) { return s1; }
}
class Program
{
    static int? N1() { return 1; }
    static int? N2() { return 2; }
    static S? N3() { return null; }
    static S? N4() { return null; }

    static int? M1()
    {
        return -(N1() + N2());
    }

    static S? M2()
    {
        return -~(N3() + N4());
    }
    static void Main() { }
}

";
            string expectedOutput = "";

            string expectedIL1 = @"{
  // Code size       61 (0x3d)
  .maxstack  2
  .locals init (int? V_0,
  int? V_1,
  int? V_2)
  IL_0000:  call       ""int? Program.N1()""
  IL_0005:  stloc.0
  IL_0006:  call       ""int? Program.N2()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_0
  IL_000e:  call       ""bool int?.HasValue.get""
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool int?.HasValue.get""
  IL_001a:  and
  IL_001b:  brtrue.s   IL_0027
  IL_001d:  ldloca.s   V_2
  IL_001f:  initobj    ""int?""
  IL_0025:  ldloc.2
  IL_0026:  ret
  IL_0027:  ldloca.s   V_0
  IL_0029:  call       ""int int?.GetValueOrDefault()""
  IL_002e:  ldloca.s   V_1
  IL_0030:  call       ""int int?.GetValueOrDefault()""
  IL_0035:  add
  IL_0036:  neg
  IL_0037:  newobj     ""int?..ctor(int)""
  IL_003c:  ret
}
";
            string expectedIL2 = @"{
  // Code size       74 (0x4a)
  .maxstack  2
  .locals init (S? V_0,
  S? V_1,
  S? V_2)
  IL_0000:  call       ""S? Program.N3()""
  IL_0005:  stloc.0
  IL_0006:  call       ""S? Program.N4()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_0
  IL_000e:  call       ""bool S?.HasValue.get""
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool S?.HasValue.get""
  IL_001a:  and
  IL_001b:  brtrue.s   IL_0027
  IL_001d:  ldloca.s   V_2
  IL_001f:  initobj    ""S?""
  IL_0025:  ldloc.2
  IL_0026:  ret
  IL_0027:  ldloca.s   V_0
  IL_0029:  call       ""S S?.GetValueOrDefault()""
  IL_002e:  ldloca.s   V_1
  IL_0030:  call       ""S S?.GetValueOrDefault()""
  IL_0035:  call       ""S S.op_Addition(S, S)""
  IL_003a:  call       ""S S.op_OnesComplement(S)""
  IL_003f:  call       ""S S.op_UnaryNegation(S)""
  IL_0044:  newobj     ""S?..ctor(S)""
  IL_0049:  ret
}";
            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
        }

        [Fact]
        public void TestLiftedBinaryOpWithConstantOnTopOfLifted()
        {
            // If we have a lifted binary operation "on top" of a lifted operation,
            // and the right side of the outer operation is a constant, then we
            // can eliminate several temporaries.
            //
            // For example, suppose we have
            //
            // return N1() * N2() + 1;
            //
            // Where N1() and N2() return int?. We could do this the obvious way:
            //
            // int? n1 = N1();
            // int? n2 = N2();
            // int? r = n1.HasValue && n2.HasValue ? new int?(n1.Value * n2.Value) : new int?();
            // int v = 1;
            // return r.HasValue ? new int?(r.Value + v)) : new int?();
            //
            // But r and v are both unnecessary. We could instead realize this as:
            //
            // int? n1 = N1();
            // int? n2 = N2();
            // return n1.HasValue && n2.HasValue ? new int?(n1.Value * n2.Value + 1) : new int?();
            //
            // We want to do this optimization in particular because it makes codegen for i++
            // and i+=1 better.
            //
            // The dev10 compiler does this optimization *only* on i++ and not on expressions
            // like N1() * N2() + 1 or sh+=1;

            string source = @"
class Program
{
    static int? N1() { return 1; }
    static int? N2() { return 2; }
    static short? sh;

    static int? M1()
    {
        return N1() * N2() + 1;
    }

    static short? M2()
    {
       return sh++;
    }

    static short? M3()
    {
       return ++sh;
    }

    static short? M4()
    {
       return sh += 1;
    }

    static void Main() { }
}
";
            string expectedOutput = "";

            string expectedIL1 = @"{
  // Code size       62 (0x3e)
  .maxstack  2
  .locals init (int? V_0,
  int? V_1,
  int? V_2)
  IL_0000:  call       ""int? Program.N1()""
  IL_0005:  stloc.0
  IL_0006:  call       ""int? Program.N2()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_0
  IL_000e:  call       ""bool int?.HasValue.get""
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool int?.HasValue.get""
  IL_001a:  and
  IL_001b:  brtrue.s   IL_0027
  IL_001d:  ldloca.s   V_2
  IL_001f:  initobj    ""int?""
  IL_0025:  ldloc.2
  IL_0026:  ret
  IL_0027:  ldloca.s   V_0
  IL_0029:  call       ""int int?.GetValueOrDefault()""
  IL_002e:  ldloca.s   V_1
  IL_0030:  call       ""int int?.GetValueOrDefault()""
  IL_0035:  mul
  IL_0036:  ldc.i4.1
  IL_0037:  add
  IL_0038:  newobj     ""int?..ctor(int)""
  IL_003d:  ret
}";

            string expectedIL2 = @"{
  // Code size       48 (0x30)
  .maxstack  3
  .locals init (short? V_0,
  short? V_1)
  IL_0000:  ldsfld     ""short? Program.sh""
  IL_0005:  dup
  IL_0006:  stloc.0
  IL_0007:  ldloca.s   V_0
  IL_0009:  call       ""bool short?.HasValue.get""
  IL_000e:  brtrue.s   IL_001b
  IL_0010:  ldloca.s   V_1
  IL_0012:  initobj    ""short?""
  IL_0018:  ldloc.1
  IL_0019:  br.s       IL_002a
  IL_001b:  ldloca.s   V_0
  IL_001d:  call       ""short short?.GetValueOrDefault()""
  IL_0022:  ldc.i4.1
  IL_0023:  add
  IL_0024:  conv.i2
  IL_0025:  newobj     ""short?..ctor(short)""
  IL_002a:  stsfld     ""short? Program.sh""
  IL_002f:  ret
}";
            string expectedIL3 = @"{
  // Code size       48 (0x30)
  .maxstack  2
  .locals init (short? V_0,
  short? V_1)
  IL_0000:  ldsfld     ""short? Program.sh""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool short?.HasValue.get""
  IL_000d:  brtrue.s   IL_001a
  IL_000f:  ldloca.s   V_1
  IL_0011:  initobj    ""short?""
  IL_0017:  ldloc.1
  IL_0018:  br.s       IL_0029
  IL_001a:  ldloca.s   V_0
  IL_001c:  call       ""short short?.GetValueOrDefault()""
  IL_0021:  ldc.i4.1
  IL_0022:  add
  IL_0023:  conv.i2
  IL_0024:  newobj     ""short?..ctor(short)""
  IL_0029:  dup
  IL_002a:  stsfld     ""short? Program.sh""
  IL_002f:  ret
}";
            string expectedIL4 = @"{
  // Code size       48 (0x30)
  .maxstack  2
  .locals init (short? V_0,
  short? V_1)
  IL_0000:  ldsfld     ""short? Program.sh""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool short?.HasValue.get""
  IL_000d:  brtrue.s   IL_001a
  IL_000f:  ldloca.s   V_1
  IL_0011:  initobj    ""short?""
  IL_0017:  ldloc.1
  IL_0018:  br.s       IL_0029
  IL_001a:  ldloca.s   V_0
  IL_001c:  call       ""short short?.GetValueOrDefault()""
  IL_0021:  ldc.i4.1
  IL_0022:  add
  IL_0023:  conv.i2
  IL_0024:  newobj     ""short?..ctor(short)""
  IL_0029:  dup
  IL_002a:  stsfld     ""short? Program.sh""
  IL_002f:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
            comp.VerifyIL("Program.M4", expectedIL4);
        }



        [Fact]
        public void TestNullableComparisonOpsBothAlwaysNull()
        {
            // An ==, !=, <, >, <= or >= operation where both operands
            // are null is always true for equality, and always false otherwise.

            // Note that the native compiler has a bug; it does not produce the warning
            // "comparing null with S? always produces false" -- it incorrectly warns
            // that it produces a null of type bool? !  Roslyn does not reproduce this bug.

            string source = @"
struct S // User-defined relational ops
{
  public static bool operator ==(S x, S y) { return true; }
  public static bool operator !=(S x, S y) { return true; }
  public static bool operator <(S x, S y) { return true; }
  public static bool operator >(S x, S y) { return true; }
  public static bool operator <=(S x, S y) { return true; }
  public static bool operator >=(S x, S y) { return true; }
  public override bool Equals(object x) { return true; }
  public override int GetHashCode() { return 0; }
}
struct T  // no user-defined relational ops
{
}
class Program
{
    static bool M1()
    {
        return new int?() == new short?();
    }
    static bool M2()
    {
        return default(double?) != new short?();
    }
    static bool M3()
    {
        return ((int?)null) < new decimal?();
    }
    static bool M4()
    {
        return new S?() == new S?();
    }
    static bool M5()
    {
        return default(S?) != new S?();
    }
    static bool M6()
    {
        return ((S?)null) < new S?();
    }
    static bool M7() // Special case for equality with null literal and no overloaded operators.
    {
        return default(T?) == null;
    }
    static bool M8()
    {
        return null != new T?();
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedILTrue = @"{
  // Code size        2 (0x2)
  .maxstack  1
  IL_0000:  ldc.i4.1
  IL_0001:  ret
}";
            string expectedILFalse = @"{
  // Code size        2 (0x2)
  .maxstack  1
  IL_0000:  ldc.i4.0
  IL_0001:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics(
// (25,16): warning CS0464: Comparing with null of type 'decimal?' always produces 'false'
//         return ((int?)null) < new decimal?();
Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < new decimal?()").WithArguments("decimal?"),
// (37,16): warning CS0464: Comparing with null of type 'S?' always produces 'false'
//         return ((S?)null) < new S?();
Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < new S?()").WithArguments("S?"));
            comp.VerifyIL("Program.M1", expectedILTrue);
            comp.VerifyIL("Program.M2", expectedILFalse);
            comp.VerifyIL("Program.M3", expectedILFalse);
            comp.VerifyIL("Program.M4", expectedILTrue);
            comp.VerifyIL("Program.M5", expectedILFalse);
            comp.VerifyIL("Program.M6", expectedILFalse);
            comp.VerifyIL("Program.M7", expectedILTrue);
            comp.VerifyIL("Program.M8", expectedILFalse);
        }

        [Fact]
        public void TestNullableComparisonNonNullWithLiteralNull()
        {
            // We can optimize this away to simply evaluating N() for its side effects
            // and returning false.
            string source = @"
struct S {}
class Program
{
    static S N() { System.Console.WriteLine(123); return new S(); }
    static bool M()
    {
        return new S?(N()) == null;
    }
    static void Main() {M();}
}
";
            string expectedOutput = "123";
            string expectedIL = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""S Program.N()""
  IL_0005:  pop
  IL_0006:  ldc.i4.0
  IL_0007:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyIL("Program.M", expectedIL);
        }

        [Fact]
        public void TestNullableComparisonOpsBothNeverNull()
        {
            // An ==, !=, <, >, <= or >= operation where both operands
            // are not null simply drops the lifting logic entirely.

            string source = @"
struct S // User-defined relational ops
{
  public static bool operator ==(S x, S y) { return true; }
  public static bool operator !=(S x, S y) { return true; }
  public static bool operator <(S x, S y) { return true; }
  public static bool operator >(S x, S y) { return true; }
  public static bool operator <=(S x, S y) { return true; }
  public static bool operator >=(S x, S y) { return true; }
  public override bool Equals(object x) { return true; }
  public override int GetHashCode() { return 0; }
}
class Program
{
    static int N1() { return 123; }
    static short N2() { return 123; }
    static double N3() { return 123; }
    static decimal N4() { return 123; }
    static S N5() { return new S(); }
    static bool M1()
    {
        // Notice that there are two optimizations here and in the next few cases. 
        // First we optimize the conversion from short? to int? so that the right 
        // hand side is new int?((int)N2()). Second, we optimize the comparison to 
        // N1() == (int)N2(), eliminating all the lifting.

        return new int?(N1()) == new short?(N2());
    }
    static bool M2()
    {
        return new double?(N3()) != new short?(N2());
    }
    
    static bool M3()
    {
        return new int?(N1()) < new decimal?(N4());
    }
    static bool M4()
    {
        return new S?(N5()) == new S?(N5());
    }
    static bool M5()
    {
        return new S?(N5()) != new S?(N5());
    }
    static bool M6()
    {
        return new S?(N5()) < new S?(N5());
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       13 (0xd)
  .maxstack  2
  IL_0000:  call       ""int Program.N1()""
  IL_0005:  call       ""short Program.N2()""
  IL_000a:  ceq
  IL_000c:  ret
}";
            string expectedIL2 = @"{
  // Code size       17 (0x11)
  .maxstack  2
  IL_0000:  call       ""double Program.N3()""
  IL_0005:  call       ""short Program.N2()""
  IL_000a:  conv.r8
  IL_000b:  ceq
  IL_000d:  ldc.i4.0
  IL_000e:  ceq
  IL_0010:  ret
}";
            string expectedIL3 = @"{
  // Code size       21 (0x15)
  .maxstack  2
  IL_0000:  call       ""int Program.N1()""
  IL_0005:  call       ""decimal decimal.op_Implicit(int)""
  IL_000a:  call       ""decimal Program.N4()""
  IL_000f:  call       ""bool decimal.op_LessThan(decimal, decimal)""
  IL_0014:  ret
}
";
            string expectedIL4 = @"{
  // Code size       16 (0x10)
  .maxstack  2
  IL_0000:  call       ""S Program.N5()""
  IL_0005:  call       ""S Program.N5()""
  IL_000a:  call       ""bool S.op_Equality(S, S)""
  IL_000f:  ret
}";
            string expectedIL5 = @"{
  // Code size       16 (0x10)
  .maxstack  2
  IL_0000:  call       ""S Program.N5()""
  IL_0005:  call       ""S Program.N5()""
  IL_000a:  call       ""bool S.op_Inequality(S, S)""
  IL_000f:  ret
}";
            string expectedIL6 = @"{
  // Code size       16 (0x10)
  .maxstack  2
  IL_0000:  call       ""S Program.N5()""
  IL_0005:  call       ""S Program.N5()""
  IL_000a:  call       ""bool S.op_LessThan(S, S)""
  IL_000f:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
            comp.VerifyIL("Program.M4", expectedIL4);
            comp.VerifyIL("Program.M5", expectedIL5);
            comp.VerifyIL("Program.M6", expectedIL6);
        }

        [Fact, WorkItem(663, "https://github.com/dotnet/roslyn/issues/663")]
        public void TestNullableComparisonOpsOneNullOneNonNull()
        {
            // An ==, !=, <, >, <= or >= operation where one operand is null and the
            // other is non-null is always false except for inequality, which is true.
            // We can skip the lifting and just generate the side effect.

            // Note that Roslyn produces considerably more warnings here than the
            // native compiler; the native compiler only produces warnings for
            // "((int?)null) < new decimal?(N3())" and "((S?)null) < new S?(N4())".
            // For compatibility Roslyn reports the same diagnostics by default,
            // but in "strict" mode (which will be part of the "warning waves" once
            // we do that) Roslyn will report warnings for
            // new S() == null and new S() != null.

            string source = @"
struct S // User-defined relational ops
{
  public static bool operator ==(S x, S y) { return true; }
  public static bool operator !=(S x, S y) { return true; }
  public static bool operator <(S x, S y) { return true; }
  public static bool operator >(S x, S y) { return true; }
  public static bool operator <=(S x, S y) { return true; }
  public static bool operator >=(S x, S y) { return true; }
  public override bool Equals(object x) { return true; }
  public override int GetHashCode() { return 0; }
}
class Program
{
    static int N1() { return 1; }
    static short N2() { return 1; }
    static decimal N3() { return 1; }
    static S N4() { return new S(); }
    static bool M1()
    {
        return new int?(N1()) == new short?();
    }
    static bool M2()
    {
        return default(double?) != new short?(N2());
    }
    static bool M3()
    {
        return ((int?)null) < new decimal?(N3());
    }
    static bool M4()
    {
        return new S?() == new S?(N4());
    }
    static bool M5()
    {
        return default(S?) != new S?(N4());
    }
    static bool M6()
    {
        return ((S?)null) < new S?(N4());
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""int Program.N1()""
  IL_0005:  pop
  IL_0006:  ldc.i4.0
  IL_0007:  ret
}";
            string expectedIL2 = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""short Program.N2()""
  IL_0005:  pop
  IL_0006:  ldc.i4.1
  IL_0007:  ret
}";
            string expectedIL3 = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""decimal Program.N3()""
  IL_0005:  pop
  IL_0006:  ldc.i4.0
  IL_0007:  ret
}";
            string expectedIL4 = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""S Program.N4()""
  IL_0005:  pop
  IL_0006:  ldc.i4.0
  IL_0007:  ret
}";
            string expectedIL5 = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""S Program.N4()""
  IL_0005:  pop
  IL_0006:  ldc.i4.1
  IL_0007:  ret
}";
            string expectedIL6 = expectedIL4;

            CompileAndVerify(source, expectedOutput: expectedOutput).VerifyDiagnostics(
                // (21,16): warning CS0472: The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'short?'
                //         return new int?(N1()) == new short?();
                Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "new int?(N1()) == new short?()").WithArguments("false", "int", "short?").WithLocation(21, 16),
                // (25,16): warning CS0472: The result of the expression is always 'true' since a value of type 'double' is never equal to 'null' of type 'double?'
                //         return default(double?) != new short?(N2());
                Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "default(double?) != new short?(N2())").WithArguments("true", "double", "double?").WithLocation(25, 16),
                // (29,16): warning CS0464: Comparing with null of type 'int?' always produces 'false'
                //         return ((int?)null) < new decimal?(N3());
                Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < new decimal?(N3())").WithArguments("int?").WithLocation(29, 16),
                // (41,16): warning CS0464: Comparing with null of type 'S?' always produces 'false'
                //         return ((S?)null) < new S?(N4());
                Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < new S?(N4())").WithArguments("S?").WithLocation(41, 16)
                );
            var comp = CompileAndVerify(source, expectedOutput: expectedOutput, options: TestOptions.ReleaseExe, parseOptions: TestOptions.Regular.WithStrictFeature());
            comp.VerifyDiagnostics(
                // (21,16): warning CS0472: The result of the expression is always 'false' since a value of type 'int' is never equal to 'null' of type 'short?'
                //         return new int?(N1()) == new short?();
                Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "new int?(N1()) == new short?()").WithArguments("false", "int", "short?").WithLocation(21, 16),
                // (25,16): warning CS0472: The result of the expression is always 'true' since a value of type 'double' is never equal to 'null' of type 'double?'
                //         return default(double?) != new short?(N2());
                Diagnostic(ErrorCode.WRN_NubExprIsConstBool, "default(double?) != new short?(N2())").WithArguments("true", "double", "double?").WithLocation(25, 16),
                // (29,16): warning CS0464: Comparing with null of type 'int?' always produces 'false'
                //         return ((int?)null) < new decimal?(N3());
                Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < new decimal?(N3())").WithArguments("int?").WithLocation(29, 16),
                // (33,16): warning CS8073: The result of the expression is always 'false' since a value of type 'S' is never equal to 'null' of type 'S?'
                //         return new S?() == new S?(N4());
                Diagnostic(ErrorCode.WRN_NubExprIsConstBool2, "new S?() == new S?(N4())").WithArguments("false", "S", "S?").WithLocation(33, 16),
                // (37,16): warning CS8073: The result of the expression is always 'true' since a value of type 'S' is never equal to 'null' of type 'S?'
                //         return default(S?) != new S?(N4());
                Diagnostic(ErrorCode.WRN_NubExprIsConstBool2, "default(S?) != new S?(N4())").WithArguments("true", "S", "S?").WithLocation(37, 16),
                // (41,16): warning CS0464: Comparing with null of type 'S?' always produces 'false'
                //         return ((S?)null) < new S?(N4());
                Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < new S?(N4())").WithArguments("S?").WithLocation(41, 16)
                );
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
            comp.VerifyIL("Program.M4", expectedIL4);
            comp.VerifyIL("Program.M5", expectedIL5);
            comp.VerifyIL("Program.M6", expectedIL6);
        }

        [Fact]
        public void TestNullableComparisonOpsOneNullOneUnknown()
        {
            // An <, >, <= or >= operation where one operand is null and the
            // other is unknown is always false; we can skip the lifting and
            // generate the side effect.
            // 
            // An == or != operation where one operand is null and the other is 
            // unknown turns into a call to HasValue.
            //
            // As mentioned above, the native compiler gets one of the warnings wrong;
            // Roslyn gets it right.

            string source = @"
struct S // User-defined relational ops
{
  public static bool operator ==(S x, S y) { return true; }
  public static bool operator !=(S x, S y) { return true; }
  public static bool operator <(S x, S y) { return true; }
  public static bool operator >(S x, S y) { return true; }
  public static bool operator <=(S x, S y) { return true; }
  public static bool operator >=(S x, S y) { return true; }
  public override bool Equals(object x) { return true; }
  public override int GetHashCode() { return 0; }
}
class Program
{
    static int? N1() { return 1; }
    static short? N2() { return 1; }
    static decimal? N3() { return 1; }
    static S? N4() { return new S(); }
    static bool M1()
    {
        return N1() == new short?();
    }
    static bool M2()
    {
        return default(double?) != N2();
    }
    static bool M3()
    {
        return ((int?)null) < N3();
    }
    static bool M4()
    {
        return new S?() == N4();
    }
    static bool M5()
    {
        return default(S?) != N4();
    }
    static bool M6()
    {
        return ((S?)null) < N4();
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       17 (0x11)
  .maxstack  2
  .locals init (int? V_0)
  IL_0000:  call       ""int? Program.N1()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool int?.HasValue.get""
  IL_000d:  ldc.i4.0
  IL_000e:  ceq
  IL_0010:  ret
}";
            // TODO: Roslyn and the native compiler both produce this sub-optimal code for
            // TODO: "default(double?) != N2()". We are essentially generating:
            // TODO:
            // TODO: short? temp1 = N2();
            // TODO: double? temp2 = temp1.HasValue ? new double?((double)temp1.GetValueOrDefault()) : new double?();
            // TODO: return temp2.HasValue;
            // TODO:
            // TODO: We could be instead simply generating
            // TODO:
            // TODO: return N2().HasValue();

            string expectedIL2 = @"{
  // Code size       48 (0x30)
  .maxstack  1
  .locals init (short? V_0,
  double? V_1)
  IL_0000:  call       ""short? Program.N2()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool short?.HasValue.get""
  IL_000d:  brtrue.s   IL_001a
  IL_000f:  ldloca.s   V_1
  IL_0011:  initobj    ""double?""
  IL_0017:  ldloc.1
  IL_0018:  br.s       IL_0027
  IL_001a:  ldloca.s   V_0
  IL_001c:  call       ""short short?.GetValueOrDefault()""
  IL_0021:  conv.r8
  IL_0022:  newobj     ""double?..ctor(double)""
  IL_0027:  stloc.1
  IL_0028:  ldloca.s   V_1
  IL_002a:  call       ""bool double?.HasValue.get""
  IL_002f:  ret
}";
            string expectedIL3 = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""decimal? Program.N3()""
  IL_0005:  pop
  IL_0006:  ldc.i4.0
  IL_0007:  ret
}";
            string expectedIL4 = @"{
  // Code size       17 (0x11)
  .maxstack  2
  .locals init (S? V_0)
  IL_0000:  call       ""S? Program.N4()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool S?.HasValue.get""
  IL_000d:  ldc.i4.0
  IL_000e:  ceq
  IL_0010:  ret
}";
            string expectedIL5 = @"{
  // Code size       14 (0xe)
  .maxstack  1
  .locals init (S? V_0)
  IL_0000:  call       ""S? Program.N4()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool S?.HasValue.get""
  IL_000d:  ret
}";
            string expectedIL6 = @"{
  // Code size        8 (0x8)
  .maxstack  1
  IL_0000:  call       ""S? Program.N4()""
  IL_0005:  pop
  IL_0006:  ldc.i4.0
  IL_0007:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics(
// (29,16): warning CS0464: Comparing with null of type 'int?' always produces 'false'
//         return ((int?)null) < N3();
Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((int?)null) < N3()").WithArguments("int?"),
// (41,16): warning CS0464: Comparing with null of type 'S?' always produces 'false'
//         return ((S?)null) < N4();
Diagnostic(ErrorCode.WRN_CmpAlwaysFalse, "((S?)null) < N4()").WithArguments("S?")
            );
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
            comp.VerifyIL("Program.M4", expectedIL4);
            comp.VerifyIL("Program.M5", expectedIL5);
            comp.VerifyIL("Program.M6", expectedIL6);
        }


        [Fact]
        public void TestNullableComparisonOpsOneNonNullOneUnknown()
        {
            // When we have a lifted comparison where we know that one side
            // is definitely not null, but know nothing about the other, then
            // we make a slight modification to the code generation. For example,
            // suppose X() and Y() return int?. For "return X() < Y();" we would generate:
            // int? x = X(); 
            // int? y = Y();
            // return x.GetValueOrDefault() < y.GetValueOrDefault() ? x.HasValue & y.HasValue : false;
            // 
            // But suppose Z() returns int. For X() < Z(), rather than converting Z() to int? and doing the
            // same codegen as before, we simplify the codegen to:
            //
            // int? x = X(); 
            // int z = Z();
            // return x.GetValueOrDefault() < z ? x.HasValue : false;
            //
            // We apply this same pattern to all lifted comparison operators.

            string source = @"
struct S // User-defined relational ops
{
    public static bool operator ==(S x, S y) { return true; }
    public static bool operator !=(S x, S y) { return true; }
    public static bool operator <(S x, S y) { return true; }
    public static bool operator >(S x, S y) { return true; }
    public static bool operator <=(S x, S y) { return true; }
    public static bool operator >=(S x, S y) { return true; }
    public override bool Equals(object x) { return true; }
    public override int GetHashCode() { return 0; }
}
class Program
{
    static int? N1() { return 1; }
    static short? N2() { return 1; }
    static decimal? N3() { return 1; }
    static S? N4() { return new S(); }

    static int V1() { return 1; }
    static short V2() { return 1; }
    static decimal V3() { return 1; }
    static S V4() { return new S(); }

    static bool M1()
    {
        return N1() == new short?(V2());
    }
    static bool M2()
    {
        return N1() < new decimal?(V3());
    }
    static bool M3()
    {
        return new S?(V4()) == N4();
    }
    static bool M4()
    {
        return new S?(V4()) != N4();
    }
    static bool M5()
    {
        return new S?(V4()) < N4();
    }
    static void Main() { }
}

";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       31 (0x1f)
  .maxstack  2
  .locals init (int? V_0,
                int V_1)
  IL_0000:  call       ""int? Program.N1()""
  IL_0005:  stloc.0
  IL_0006:  call       ""short Program.V2()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_0
  IL_000e:  call       ""int int?.GetValueOrDefault()""
  IL_0013:  ldloc.1
  IL_0014:  ceq
  IL_0016:  ldloca.s   V_0
  IL_0018:  call       ""bool int?.HasValue.get""
  IL_001d:  and
  IL_001e:  ret
}";

            // TODO: We do a worse job than the native compiler here. Find out why.

            string expectedIL2 = @"{
  // Code size       72 (0x48)
  .maxstack  2
  .locals init (decimal? V_0,
                decimal V_1,
                int? V_2,
                decimal? V_3)
  IL_0000:  call       ""int? Program.N1()""
  IL_0005:  stloc.2
  IL_0006:  ldloca.s   V_2
  IL_0008:  call       ""bool int?.HasValue.get""
  IL_000d:  brtrue.s   IL_001a
  IL_000f:  ldloca.s   V_3
  IL_0011:  initobj    ""decimal?""
  IL_0017:  ldloc.3
  IL_0018:  br.s       IL_002b
  IL_001a:  ldloca.s   V_2
  IL_001c:  call       ""int int?.GetValueOrDefault()""
  IL_0021:  call       ""decimal decimal.op_Implicit(int)""
  IL_0026:  newobj     ""decimal?..ctor(decimal)""
  IL_002b:  stloc.0
  IL_002c:  call       ""decimal Program.V3()""
  IL_0031:  stloc.1
  IL_0032:  ldloca.s   V_0
  IL_0034:  call       ""decimal decimal?.GetValueOrDefault()""
  IL_0039:  ldloc.1
  IL_003a:  call       ""bool decimal.op_LessThan(decimal, decimal)""
  IL_003f:  ldloca.s   V_0
  IL_0041:  call       ""bool decimal?.HasValue.get""
  IL_0046:  and
  IL_0047:  ret
}";
            string expectedIL3 = @"{
  // Code size       37 (0x25)
  .maxstack  2
  .locals init (S V_0,
  S? V_1)
  IL_0000:  call       ""S Program.V4()""
  IL_0005:  stloc.0
  IL_0006:  call       ""S? Program.N4()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_1
  IL_000e:  call       ""bool S?.HasValue.get""
  IL_0013:  brtrue.s   IL_0017
  IL_0015:  ldc.i4.0
  IL_0016:  ret
  IL_0017:  ldloc.0
  IL_0018:  ldloca.s   V_1
  IL_001a:  call       ""S S?.GetValueOrDefault()""
  IL_001f:  call       ""bool S.op_Equality(S, S)""
  IL_0024:  ret
}";
            string expectedIL4 = @"{
  // Code size       37 (0x25)
  .maxstack  2
  .locals init (S V_0,
  S? V_1)
  IL_0000:  call       ""S Program.V4()""
  IL_0005:  stloc.0
  IL_0006:  call       ""S? Program.N4()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_1
  IL_000e:  call       ""bool S?.HasValue.get""
  IL_0013:  brtrue.s   IL_0017
  IL_0015:  ldc.i4.1
  IL_0016:  ret
  IL_0017:  ldloc.0
  IL_0018:  ldloca.s   V_1
  IL_001a:  call       ""S S?.GetValueOrDefault()""
  IL_001f:  call       ""bool S.op_Inequality(S, S)""
  IL_0024:  ret
}";
            string expectedIL5 = @"{
  // Code size       37 (0x25)
  .maxstack  2
  .locals init (S V_0,
  S? V_1)
  IL_0000:  call       ""S Program.V4()""
  IL_0005:  stloc.0
  IL_0006:  call       ""S? Program.N4()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_1
  IL_000e:  call       ""bool S?.HasValue.get""
  IL_0013:  brtrue.s   IL_0017
  IL_0015:  ldc.i4.0
  IL_0016:  ret
  IL_0017:  ldloc.0
  IL_0018:  ldloca.s   V_1
  IL_001a:  call       ""S S?.GetValueOrDefault()""
  IL_001f:  call       ""bool S.op_LessThan(S, S)""
  IL_0024:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
            comp.VerifyIL("Program.M4", expectedIL4);
            comp.VerifyIL("Program.M5", expectedIL5);
        }

        [Fact]
        public void TestLiftedConversionOnTopOfLifted()
        {
            // Here's an optimization that the dev10 compiler does not do except in some special cases.
            // If we have a lifted conversion "on top" of another lifted operation, then the lifted
            // conversion can be "distributed" to both branches of the underlying lifted operation.
            //
            // For example, suppose we have
            //
            // return (double?)(N1() + N2());
            //
            // Where N1 and N2 return int?. The dev10 compiler does this in two steps: first it 
            // computes the int? result of the addition, and then it converts that int? to double?
            // with a lifted conversion. Basically, it generates:
            //
            // int? t1 = N1();
            // int? t2 = N2();
            // int? t3 = t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?();
            // double? t4 = t3.HasValue ? new double?((double)t3.Value)) : new double?();
            //
            // But t3 is completely unnecessary here. We observe that the lifted conversion:
            //
            // (double?) (t1.HasValue && t2.HasValue ? new int?(t1.Value + t2.Value) : new int?())
            //
            // Is the same as distributing the conversion to the consequence and alternative:
            //
            // (t1.HasValue && t2.HasValue ? (double?)( new int?(t1.Value + t2.Value) ): (double?) ( new int?() ) )
            // 
            // And now we can optimize the consequence and alternative down to
            //
            // (t1.HasValue && t2.HasValue ? new double?((double)(t1.Value + t2.Value) ): new double?() )
            //
            // And the int? t3 disappears entirely. 
            //
            // This optimization has the nice property that it composes well with itself. 


            string source = @"
struct S
{
  public static S operator -(S s) { return s; }
  public static implicit operator int(S s) { return 1; }
}
class Program
{
    static int? N1() { return 1; }
    static int? N2() { return 1; }
    static S? N3() { return null; }

    // Start with a nice simple case; we have a lifted numeric conversion on top of a
    // lifted addition.
    static long? M1()
    {
        return N1() + N2();
    }

    // This should also work with lifted user-defined conversions, and work on top
    // of a lifted unary operator.
    static int? M2()
    {
        return -N3();
    }
    static void Main() { }
}

";
            string expectedOutput = "";

            string expectedIL1 = @"{
  // Code size       61 (0x3d)
  .maxstack  2
  .locals init (int? V_0,
  int? V_1,
  long? V_2)
  IL_0000:  call       ""int? Program.N1()""
  IL_0005:  stloc.0
  IL_0006:  call       ""int? Program.N2()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_0
  IL_000e:  call       ""bool int?.HasValue.get""
  IL_0013:  ldloca.s   V_1
  IL_0015:  call       ""bool int?.HasValue.get""
  IL_001a:  and
  IL_001b:  brtrue.s   IL_0027
  IL_001d:  ldloca.s   V_2
  IL_001f:  initobj    ""long?""
  IL_0025:  ldloc.2
  IL_0026:  ret
  IL_0027:  ldloca.s   V_0
  IL_0029:  call       ""int int?.GetValueOrDefault()""
  IL_002e:  ldloca.s   V_1
  IL_0030:  call       ""int int?.GetValueOrDefault()""
  IL_0035:  add
  IL_0036:  conv.i8
  IL_0037:  newobj     ""long?..ctor(long)""
  IL_003c:  ret
}";
            string expectedIL2 = @"{
  // Code size       48 (0x30)
  .maxstack  1
  .locals init (S? V_0,
  int? V_1)
  IL_0000:  call       ""S? Program.N3()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool S?.HasValue.get""
  IL_000d:  brtrue.s   IL_0019
  IL_000f:  ldloca.s   V_1
  IL_0011:  initobj    ""int?""
  IL_0017:  ldloc.1
  IL_0018:  ret
  IL_0019:  ldloca.s   V_0
  IL_001b:  call       ""S S?.GetValueOrDefault()""
  IL_0020:  call       ""S S.op_UnaryNegation(S)""
  IL_0025:  call       ""int S.op_Implicit(S)""
  IL_002a:  newobj     ""int?..ctor(int)""
  IL_002f:  ret
}";
            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
        }

        [Fact]
        public void TestNullableBoolBinOpsBothAlwaysNull()
        {
            // x & y and x | y are null if both operands are null.

            string source = @"
class Program
{
    static bool? M1()
    {
        return new bool?() | new bool?();
    }
    static bool? M2()
    {
        return (bool?)null & default(bool?);
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       10 (0xa)
  .maxstack  1
  .locals init (bool? V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""bool?""
  IL_0008:  ldloc.0
  IL_0009:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL);
            comp.VerifyIL("Program.M2", expectedIL);
        }

        [Fact]
        public void TestNullableBoolBinOpsBothNotNull()
        {
            // x & y and x | y can be reduced to their non-lifted forms
            // if both operands are known to be non-null. 
            //
            // Roslyn does a slightly better job than the native compiler here.
            // The native compiler effectively generates code as though you'd written:
            //
            // bool temp1 = N();
            // bool? temp2 = new bool?(N() & N());
            // return temp1 ? new bool?(true) : temp2;
            //
            // Whereas Roslyn simply generates code as though you'd written:
            //
            // return new bool?(N() | N() & N())

            string source = @"
class Program
{
    static bool N() { return true; }
    static bool? M1()
    {
        return new bool?(N()) | new bool?(N()) & new bool?(N());
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       23 (0x17)
  .maxstack  3
  IL_0000:  call       ""bool Program.N()""
  IL_0005:  call       ""bool Program.N()""
  IL_000a:  call       ""bool Program.N()""
  IL_000f:  and
  IL_0010:  or
  IL_0011:  newobj     ""bool?..ctor(bool)""
  IL_0016:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL);
        }

        [Fact]
        public void TestNullableBoolBinOpsOneNull()
        {
            // codegen for x & y and x | y can be simplified if one operand is known to be null, 
            // and simplified even further if the other operand is known to be non-null.

            string source = @"
class Program
{
    static bool? N() { return false; }
    static bool B() { return false; }
    static bool? M1()
    {
        // Generated as temp = N(), temp.GetValueOrDefault() ? null : temp
        return N() & new bool?();
    }
    static bool? M2()
    {
        // Generated as temp = N(), temp.GetValueOrDefault() ? null : temp
        return default(bool?) & N();
    }
    static bool? M3()
    {
        // Generated as temp = N(), temp.GetValueOrDefault() ? temp : null
        return N() | new bool?();
    }
    static bool? M4()
    {
        // Generated as temp = N(), temp.GetValueOrDefault() ? temp : null
        return default(bool?) | N();
    }
    static bool? M5()
    {
        // Generated as B() ? null : new bool?(false)
        return new bool?(B()) & new bool?();
    }
    static bool? M6()
    {
        // Generated as B() ? null : new bool?(false)
        return default(bool?) & new bool?(B());
    }
    static bool? M7()
    {
        // Generated as B() ? new bool?(true) : null
        return new bool?(B()) | new bool?();
    }
    static bool? M8()
    {
        // Generated as B() ? new bool?(true) : null
        return default(bool?) | new bool?(B());
    }
    static void Main() {}
}

";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       27 (0x1b)
  .maxstack  1
  .locals init (bool? V_0,
  bool? V_1)
  IL_0000:  call       ""bool? Program.N()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool bool?.GetValueOrDefault()""
  IL_000d:  brtrue.s   IL_0011
  IL_000f:  ldloc.0
  IL_0010:  ret
  IL_0011:  ldloca.s   V_1
  IL_0013:  initobj    ""bool?""
  IL_0019:  ldloc.1
  IL_001a:  ret
}";
            string expectedIL2 = expectedIL1;
            string expectedIL3 = @"{
  // Code size       27 (0x1b)
  .maxstack  1
  .locals init (bool? V_0,
  bool? V_1)
  IL_0000:  call       ""bool? Program.N()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool bool?.GetValueOrDefault()""
  IL_000d:  brtrue.s   IL_0019
  IL_000f:  ldloca.s   V_1
  IL_0011:  initobj    ""bool?""
  IL_0017:  ldloc.1
  IL_0018:  ret
  IL_0019:  ldloc.0
  IL_001a:  ret
}";
            string expectedIL4 = expectedIL3;
            string expectedIL5 = @"{
  // Code size       24 (0x18)
  .maxstack  1
  .locals init (bool? V_0)
  IL_0000:  call       ""bool Program.B()""
  IL_0005:  brtrue.s   IL_000e
  IL_0007:  ldc.i4.0
  IL_0008:  newobj     ""bool?..ctor(bool)""
  IL_000d:  ret
  IL_000e:  ldloca.s   V_0
  IL_0010:  initobj    ""bool?""
  IL_0016:  ldloc.0
  IL_0017:  ret
}";
            string expectedIL6 = expectedIL5;
            string expectedIL7 = @"{
  // Code size       24 (0x18)
  .maxstack  1
  .locals init (bool? V_0)
  IL_0000:  call       ""bool Program.B()""
  IL_0005:  brtrue.s   IL_0011
  IL_0007:  ldloca.s   V_0
  IL_0009:  initobj    ""bool?""
  IL_000f:  ldloc.0
  IL_0010:  ret
  IL_0011:  ldc.i4.1
  IL_0012:  newobj     ""bool?..ctor(bool)""
  IL_0017:  ret
}
";
            string expectedIL8 = expectedIL7;

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
            comp.VerifyIL("Program.M4", expectedIL4);
            comp.VerifyIL("Program.M5", expectedIL5);
            comp.VerifyIL("Program.M6", expectedIL6);
            comp.VerifyIL("Program.M7", expectedIL7);
            comp.VerifyIL("Program.M8", expectedIL8);
        }

        [Fact]
        public void TestNullableBoolBinOpsOneNonNull()
        {
            // Codegen for x & y and x | y can be simplified if one operand is known to be non null.
            // Note that we have already considered the case where one operand is null and the 
            // other is non null, in the test case above.

            string source = @"
class Program
{
    static bool? N() { return false; }
    static bool B() { return false; }
    static bool? M1()
    {
        return new bool?(B()) & N();
    }
    static bool? M2()
    {
        return N() & new bool?(B());
    }
    static bool? M3()
    {
        return new bool?(B()) | N();
    }
    static bool? M4()
    {
        return N() | new bool?(B());
    }
    static void Main() {}
}

";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       22 (0x16)
  .maxstack  2
  .locals init (bool? V_0)
  IL_0000:  call       ""bool Program.B()""
  IL_0005:  call       ""bool? Program.N()""
  IL_000a:  stloc.0
  IL_000b:  brtrue.s   IL_0014
  IL_000d:  ldc.i4.0
  IL_000e:  newobj     ""bool?..ctor(bool)""
  IL_0013:  ret
  IL_0014:  ldloc.0
  IL_0015:  ret
}";
            string expectedIL2 = @"{
  // Code size       22 (0x16)
  .maxstack  1
  .locals init (bool? V_0)
  IL_0000:  call       ""bool? Program.N()""
  IL_0005:  stloc.0
  IL_0006:  call       ""bool Program.B()""
  IL_000b:  brtrue.s   IL_0014
  IL_000d:  ldc.i4.0
  IL_000e:  newobj     ""bool?..ctor(bool)""
  IL_0013:  ret
  IL_0014:  ldloc.0
  IL_0015:  ret
}";
            string expectedIL3 = @"{
  // Code size       22 (0x16)
  .maxstack  2
  .locals init (bool? V_0)
  IL_0000:  call       ""bool Program.B()""
  IL_0005:  call       ""bool? Program.N()""
  IL_000a:  stloc.0
  IL_000b:  brtrue.s   IL_000f
  IL_000d:  ldloc.0
  IL_000e:  ret
  IL_000f:  ldc.i4.1
  IL_0010:  newobj     ""bool?..ctor(bool)""
  IL_0015:  ret
}";
            string expectedIL4 = @"{
  // Code size       22 (0x16)
  .maxstack  1
  .locals init (bool? V_0)
  IL_0000:  call       ""bool? Program.N()""
  IL_0005:  stloc.0
  IL_0006:  call       ""bool Program.B()""
  IL_000b:  brtrue.s   IL_000f
  IL_000d:  ldloc.0
  IL_000e:  ret
  IL_000f:  ldc.i4.1
  IL_0010:  newobj     ""bool?..ctor(bool)""
  IL_0015:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
            comp.VerifyIL("Program.M3", expectedIL3);
            comp.VerifyIL("Program.M4", expectedIL4);
        }

        [Fact]
        public void TestNullableBinOpsBothAlwaysNull()
        {
            // x op y is null if both ops are null for the binary operators
            // * / % + - << >> and for non-bool & ^ | 

            string source = @"
class Program
{
    static long? M1()
    {
        return new int?() + new long?();
    }
    static decimal? M2()
    {
        return (short?)null * default(decimal?);
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       10 (0xa)
  .maxstack  1
  .locals init (long? V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""long?""
  IL_0008:  ldloc.0
  IL_0009:  ret
}";
            string expectedIL2 = @"{
  // Code size       10 (0xa)
  .maxstack  1
  .locals init (decimal? V_0)
  IL_0000:  ldloca.s   V_0
  IL_0002:  initobj    ""decimal?""
  IL_0008:  ldloc.0
  IL_0009:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics(
// (6,16): warning CS0458: The result of the expression is always 'null' of type 'long?'
//         return new int?() + new long?();
Diagnostic(ErrorCode.WRN_AlwaysNull, "new int?() + new long?()").WithArguments("long?"),
// (10,16): warning CS0458: The result of the expression is always 'null' of type 'decimal?'
//         return (short?)null * default(decimal?);
Diagnostic(ErrorCode.WRN_AlwaysNull, "(short?)null * default(decimal?)").WithArguments("decimal?"));
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
        }

        [Fact]
        public void TestNullableBinOpsBothNonNull()
        {
            // Lifted x op y is generated as non-lifted if both operands are known to be non-null
            // for operators * / % + - << >> and for non-bool & ^ | 
            //
            // Roslyn does a far better job of this optimization than the native compiler.

            string source = @"
class Program
{
    static int N() { return 1; }
    static int? M1()
    {
        return 
            new int?(N()) * 
            new int?(N()) / 
            new int?(N()) % 
            new int?(N()) +
            new int?(N()) - 
            new int?(N()) << 
            new int?(N()) >>
            new int?(N()) &
            new int?(N()) ^ 
            new int?(N()) |
            new int?(N());
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL = @"{
  // Code size       77 (0x4d)
  .maxstack  3
  IL_0000:  call       ""int Program.N()""
  IL_0005:  call       ""int Program.N()""
  IL_000a:  mul
  IL_000b:  call       ""int Program.N()""
  IL_0010:  div
  IL_0011:  call       ""int Program.N()""
  IL_0016:  rem
  IL_0017:  call       ""int Program.N()""
  IL_001c:  add
  IL_001d:  call       ""int Program.N()""
  IL_0022:  sub
  IL_0023:  call       ""int Program.N()""
  IL_0028:  ldc.i4.s   31
  IL_002a:  and
  IL_002b:  shl
  IL_002c:  call       ""int Program.N()""
  IL_0031:  ldc.i4.s   31
  IL_0033:  and
  IL_0034:  shr
  IL_0035:  call       ""int Program.N()""
  IL_003a:  and
  IL_003b:  call       ""int Program.N()""
  IL_0040:  xor
  IL_0041:  call       ""int Program.N()""
  IL_0046:  or
  IL_0047:  newobj     ""int?..ctor(int)""
  IL_004c:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL);
        }

        [Fact]
        public void TestNullableBinOpsOneNull()
        {
            // If we have null + N() or null + new int?(B()) 
            // then we simply generate M() as a side effect and result in null.

            string source = @"
class Program
{
    static int? N() { return 1; }
    static int B() { return 1; }

    static int? M1()
    {
        return new int?() + N();
    }
    static int? M2()
    {
        return new int?(B()) * default(int?);
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       16 (0x10)
  .maxstack  1
  .locals init (int? V_0)
  IL_0000:  call       ""int? Program.N()""
  IL_0005:  pop
  IL_0006:  ldloca.s   V_0
  IL_0008:  initobj    ""int?""
  IL_000e:  ldloc.0
  IL_000f:  ret
}";
            string expectedIL2 = @"{
  // Code size       16 (0x10)
  .maxstack  1
  .locals init (int? V_0)
  IL_0000:  call       ""int Program.B()""
  IL_0005:  pop
  IL_0006:  ldloca.s   V_0
  IL_0008:  initobj    ""int?""
  IL_000e:  ldloc.0
  IL_000f:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics(
// (9,16): warning CS0458: The result of the expression is always 'null' of type 'int?'
//         return new int?() + N();
Diagnostic(ErrorCode.WRN_AlwaysNull, "new int?() + N()").WithArguments("int?"),
// (13,16): warning CS0458: The result of the expression is always 'null' of type 'int?'
//         return new int?(B()) * default(int?);
Diagnostic(ErrorCode.WRN_AlwaysNull, "new int?(B()) * default(int?)").WithArguments("int?"));
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
        }

        [Fact]
        public void TestNullableBinOpsOneNonNull()
        {
            // If one side of the nullable binop is non-null then we skip calling HasValue and GetValueOrDefault
            // on that side.

            string source = @"
class Program
{
    static int B() { return 1; }
    static int? N() { return 1; }
    static int? M1()
    {
        return new int?(B()) + N();
    }
    static int? M2()
    {
        return new int?(1) + N();
    }
    static void Main() {}
}
";
            string expectedOutput = "";
            string expectedIL1 = @"{
  // Code size       46 (0x2e)
  .maxstack  2
  .locals init (int V_0,
  int? V_1,
  int? V_2)
  IL_0000:  call       ""int Program.B()""
  IL_0005:  stloc.0
  IL_0006:  call       ""int? Program.N()""
  IL_000b:  stloc.1
  IL_000c:  ldloca.s   V_1
  IL_000e:  call       ""bool int?.HasValue.get""
  IL_0013:  brtrue.s   IL_001f
  IL_0015:  ldloca.s   V_2
  IL_0017:  initobj    ""int?""
  IL_001d:  ldloc.2
  IL_001e:  ret
  IL_001f:  ldloc.0
  IL_0020:  ldloca.s   V_1
  IL_0022:  call       ""int int?.GetValueOrDefault()""
  IL_0027:  add
  IL_0028:  newobj     ""int?..ctor(int)""
  IL_002d:  ret
}";


            // TODO: Roslyn does a slightly worse job here than the native compiler does.
            // TODO: The native compiler knows that the constant need not be stored in a temporary.
            // TODO: We will clean this up in a later checkin.
            // TODO: When we do so, add tests for ++ -- +=, etc.

            string expectedIL2 = @"
{
  // Code size       40 (0x28)
  .maxstack  2
  .locals init (int? V_0,
                int? V_1)
  IL_0000:  call       ""int? Program.N()""
  IL_0005:  stloc.0
  IL_0006:  ldloca.s   V_0
  IL_0008:  call       ""bool int?.HasValue.get""
  IL_000d:  brtrue.s   IL_0019
  IL_000f:  ldloca.s   V_1
  IL_0011:  initobj    ""int?""
  IL_0017:  ldloc.1
  IL_0018:  ret
  IL_0019:  ldc.i4.1
  IL_001a:  ldloca.s   V_0
  IL_001c:  call       ""int int?.GetValueOrDefault()""
  IL_0021:  add
  IL_0022:  newobj     ""int?..ctor(int)""
  IL_0027:  ret
}";

            var comp = CompileAndVerify(source, expectedOutput: expectedOutput);
            comp.VerifyDiagnostics();
            comp.VerifyIL("Program.M1", expectedIL1);
            comp.VerifyIL("Program.M2", expectedIL2);
        }

        [Fact]
        public void TestNullableBinOpsOneZero()
        {
            // If one side of the nullable binop is non-null then we skip calling HasValue and GetValueOrDefault
            // on that side.

            string source = @"
class Program
{
    static void Main() 
    {
        int? N = 42;
        System.Console.WriteLine(0 + N);
        System.Console.WriteLine(N + 0);
        System.Console.WriteLine(0 - N);
        System.Console.WriteLine(N - 0);
        System.Console.WriteLine(0 * N);
        System.Console.WriteLine(N * 0);
        System.Console.WriteLine(1 * N);
        System.Console.WriteLine(N * 1);
    }
}
";


            var comp = CompileAndVerify(source, expectedOutput: @"
42
42
-42
42
0
0
42
42")
                .VerifyIL("Program.Main",
 @"
{
  // Code size      349 (0x15d)
  .maxstack  3
  .locals init (int? V_0,
                int? V_1)
  IL_0000:  ldc.i4.s   42
  IL_0002:  newobj     ""int?..ctor(int)""
  IL_0007:  dup
  IL_0008:  stloc.0
  IL_0009:  ldloca.s   V_0
  IL_000b:  call       ""bool int?.HasValue.get""
  IL_0010:  brtrue.s   IL_001d
  IL_0012:  ldloca.s   V_1
  IL_0014:  initobj    ""int?""
  IL_001a:  ldloc.1
  IL_001b:  br.s       IL_0029
  IL_001d:  ldloca.s   V_0
  IL_001f:  call       ""int int?.GetValueOrDefault()""
  IL_0024:  newobj     ""int?..ctor(int)""
  IL_0029:  box        ""int?""
  IL_002e:  call       ""void System.Console.WriteLine(object)""
  IL_0033:  dup
  IL_0034:  stloc.0
  IL_0035:  ldloca.s   V_0
  IL_0037:  call       ""bool int?.HasValue.get""
  IL_003c:  brtrue.s   IL_0049
  IL_003e:  ldloca.s   V_1
  IL_0040:  initobj    ""int?""
  IL_0046:  ldloc.1
  IL_0047:  br.s       IL_0055
  IL_0049:  ldloca.s   V_0
  IL_004b:  call       ""int int?.GetValueOrDefault()""
  IL_0050:  newobj     ""int?..ctor(int)""
  IL_0055:  box        ""int?""
  IL_005a:  call       ""void System.Console.WriteLine(object)""
  IL_005f:  dup
  IL_0060:  stloc.0
  IL_0061:  ldloca.s   V_0
  IL_0063:  call       ""bool int?.HasValue.get""
  IL_0068:  brtrue.s   IL_0075
  IL_006a:  ldloca.s   V_1
  IL_006c:  initobj    ""int?""
  IL_0072:  ldloc.1
  IL_0073:  br.s       IL_0083
  IL_0075:  ldc.i4.0
  IL_0076:  ldloca.s   V_0
  IL_0078:  call       ""int int?.GetValueOrDefault()""
  IL_007d:  sub
  IL_007e:  newobj     ""int?..ctor(int)""
  IL_0083:  box        ""int?""
  IL_0088:  call       ""void System.Console.WriteLine(object)""
  IL_008d:  dup
  IL_008e:  stloc.0
  IL_008f:  ldloca.s   V_0
  IL_0091:  call       ""bool int?.HasValue.get""
  IL_0096:  brtrue.s   IL_00a3
  IL_0098:  ldloca.s   V_1
  IL_009a:  initobj    ""int?""
  IL_00a0:  ldloc.1
  IL_00a1:  br.s       IL_00af
  IL_00a3:  ldloca.s   V_0
  IL_00a5:  call       ""int int?.GetValueOrDefault()""
  IL_00aa:  newobj     ""int?..ctor(int)""
  IL_00af:  box        ""int?""
  IL_00b4:  call       ""void System.Console.WriteLine(object)""
  IL_00b9:  dup
  IL_00ba:  stloc.0
  IL_00bb:  ldloca.s   V_0
  IL_00bd:  call       ""bool int?.HasValue.get""
  IL_00c2:  brtrue.s   IL_00cf
  IL_00c4:  ldloca.s   V_1
  IL_00c6:  initobj    ""int?""
  IL_00cc:  ldloc.1
  IL_00cd:  br.s       IL_00d5
  IL_00cf:  ldc.i4.0
  IL_00d0:  newobj     ""int?..ctor(int)""
  IL_00d5:  box        ""int?""
  IL_00da:  call       ""void System.Console.WriteLine(object)""
  IL_00df:  dup
  IL_00e0:  stloc.0
  IL_00e1:  ldloca.s   V_0
  IL_00e3:  call       ""bool int?.HasValue.get""
  IL_00e8:  brtrue.s   IL_00f5
  IL_00ea:  ldloca.s   V_1
  IL_00ec:  initobj    ""int?""
  IL_00f2:  ldloc.1
  IL_00f3:  br.s       IL_00fb
  IL_00f5:  ldc.i4.0
  IL_00f6:  newobj     ""int?..ctor(int)""
  IL_00fb:  box        ""int?""
  IL_0100:  call       ""void System.Console.WriteLine(object)""
  IL_0105:  dup
  IL_0106:  stloc.0
  IL_0107:  ldloca.s   V_0
  IL_0109:  call       ""bool int?.HasValue.get""
  IL_010e:  brtrue.s   IL_011b
  IL_0110:  ldloca.s   V_1
  IL_0112:  initobj    ""int?""
  IL_0118:  ldloc.1
  IL_0119:  br.s       IL_0127
  IL_011b:  ldloca.s   V_0
  IL_011d:  call       ""int int?.GetValueOrDefault()""
  IL_0122:  newobj     ""int?..ctor(int)""
  IL_0127:  box        ""int?""
  IL_012c:  call       ""void System.Console.WriteLine(object)""
  IL_0131:  stloc.0
  IL_0132:  ldloca.s   V_0
  IL_0134:  call       ""bool int?.HasValue.get""
  IL_0139:  brtrue.s   IL_0146
  IL_013b:  ldloca.s   V_1
  IL_013d:  initobj    ""int?""
  IL_0143:  ldloc.1
  IL_0144:  br.s       IL_0152
  IL_0146:  ldloca.s   V_0
  IL_0148:  call       ""int int?.GetValueOrDefault()""
  IL_014d:  newobj     ""int?..ctor(int)""
  IL_0152:  box        ""int?""
  IL_0157:  call       ""void System.Console.WriteLine(object)""
  IL_015c:  ret
}");
        }

        [Fact]
        public void TestNullableBinOpsOneZero1()
        {
            // If one side of the nullable binop is non-null then we skip calling HasValue and GetValueOrDefault
            // on that side.

            string source = @"
class Program
{
    static void Main() 
    {
        System.Console.WriteLine(0 + (int?)42);
        System.Console.WriteLine((int?)42 + 0);
        System.Console.WriteLine(0 - (int?)42);
        System.Console.WriteLine((int?)42 - 0);
        System.Console.WriteLine(0 * (int?)42);
        System.Console.WriteLine((int?)42 * 0);
        System.Console.WriteLine(1 * (int?)42);
        System.Console.WriteLine((int?)42 * 1);
    }
}
";


            var comp = CompileAndVerify(source, expectedOutput: @"
42
42
-42
42
0
0
42
42")
                .VerifyIL("Program.Main",
 @"
{
  // Code size       97 (0x61)
  .maxstack  2
  IL_0000:  ldc.i4.s   42
  IL_0002:  box        ""int""
  IL_0007:  call       ""void System.Console.WriteLine(object)""
  IL_000c:  ldc.i4.s   42
  IL_000e:  box        ""int""
  IL_0013:  call       ""void System.Console.WriteLine(object)""
  IL_0018:  ldc.i4.0
  IL_0019:  ldc.i4.s   42
  IL_001b:  sub
  IL_001c:  box        ""int""
  IL_0021:  call       ""void System.Console.WriteLine(object)""
  IL_0026:  ldc.i4.s   42
  IL_0028:  box        ""int""
  IL_002d:  call       ""void System.Console.WriteLine(object)""
  IL_0032:  ldc.i4.0
  IL_0033:  box        ""int""
  IL_0038:  call       ""void System.Console.WriteLine(object)""
  IL_003d:  ldc.i4.0
  IL_003e:  box        ""int""
  IL_0043:  call       ""void System.Console.WriteLine(object)""
  IL_0048:  ldc.i4.s   42
  IL_004a:  box        ""int""
  IL_004f:  call       ""void System.Console.WriteLine(object)""
  IL_0054:  ldc.i4.s   42
  IL_0056:  box        ""int""
  IL_005b:  call       ""void System.Console.WriteLine(object)""
  IL_0060:  ret
}");
        }
    }
}
